{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Semantic Kernel\n",
    "\n",
    "Semantic-Kernel（以下简称SK），是微软开源的一个大模型轻量开发框架，它抽象出了大语言模型应用中主要的几大概念：\n",
    "- Plugins/Skills：Function集合\n",
    "- Planner: 根据任务goal，skills等信息，生成任务计划\n",
    "- Memory: 保存历史任务状态数据\n",
    "- connectors：与数据库、LLM等组件交互组件\n",
    "\n",
    "在实现上，SK主要围绕Plugin/Skill（Function集合）进行串联，把每一个功能函数/prompt配置等抽象成一个NativeFunction/SemanticFunction，以此实现更中度的功能集合，再将这些功能集合通过与LLM、数据库等组件进行交互，以实现LLM原生应用。\n",
    "![SK_flow](https://camo.githubusercontent.com/4a7cf302109121e1464bd8eb46f0b65a27b1ceb10f85f206ea9a2e44c1f50e1f/68747470733a2f2f6c6561726e2e6d6963726f736f66742e636f6d2f656e2d75732f73656d616e7469632d6b65726e656c2f6d656469612f6b65726e656c2d696e666f677261706869632e706e67)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接下来我们以一个实际的案例来说明如何使用SK+千帆SDK以实现一个基于SK Plugin的多轮对话ChatBot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 前置准备\n",
    "### 安装依赖：\n",
    "本文基于semantic-kernel `0.4.5dev0` 版本，由于 SK持续迭代的原因，原来的Skill正在迁移成Plugin，如碰到不兼容问题请检查依赖版本。\n",
    "使用以下命令可以安装我们所需要的`qianfan` 以及 `semantic-kernel`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#-# cell_skip\n",
    "! pip install \"qianfan>=0.3.0\" -U\n",
    "! pip install semantic-kernel=='0.4.5.dev0'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与直接调用千帆SDK类似，我们需要先初始化鉴权(以下以使用IAM鉴权为例)："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ[\"QIANFAN_ACCESS_KEY\"] = \"your_ak\"\n",
    "os.environ[\"QIANFAN_SECRET_kEY\"] = \"your_sk\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "初始化一个SK `kernel`， kernel是 SK中的一个重要类型，通过Kernel，我们可以把众多Plugin，LLM，以及Memory等进行注册组合，最终实现一键式的规划调用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import semantic_kernel as sk\n",
    "kernel = sk.Kernel()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "初始化兼容SK的千帆ChatCompletion service对象，并注册到kernel中：\n",
    "对于续写场景，QianfanChatCompletion也实现了对应的抽象类方法，所以可以统一使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<semantic_kernel.kernel.Kernel at 0x1153b79d0>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from qianfan.extensions.semantic_kernel.connectors.qianfan_chat_completion import QianfanChatCompletion\n",
    "\n",
    "qfchat = QianfanChatCompletion(ai_model_id=\"ERNIE-Bot-4\")\n",
    "# 注册service_id 以及对应的service对象\n",
    "kernel.add_chat_service(\n",
    "    \"qianfan\", qfchat\n",
    ")\n",
    "# kernel.add_text_completion_service(\n",
    "#     \"qianfan_text\", qfchat\n",
    "# )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义一个PromptTemplate，以及调用大模型时候会用到的config，"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptTemplate(template='', template_engine=PromptTemplateEngine(), prompt_config=PromptTemplateConfig(schema_=1, type='completion', description='', completion=AIRequestSettings(service_id=None, extension_data={}), default_services=[], parameters=[]), messages=[])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import  semantic_kernel as sk\n",
    "\n",
    "prompt_config = sk.PromptTemplateConfig()\n",
    "sk.ChatPromptTemplate(\"\", kernel.prompt_template_engine, prompt_config=prompt_config)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "abc {'schema_': FieldInfo(annotation=int, required=False, default=1, alias='schema', alias_priority=2), 'type': FieldInfo(annotation=str, required=False, default='completion'), 'description': FieldInfo(annotation=str, required=False, default=''), 'completion': FieldInfo(annotation=~AIRequestSettingsT, required=False, default_factory=AIRequestSettings), 'default_services': FieldInfo(annotation=List[str], required=False, default_factory=list), 'parameters': FieldInfo(annotation=List[ParameterView], required=False, default_factory=list)}\n"
     ]
    }
   ],
   "source": [
    "sk_prompt = \"\"\"\n",
    "你是一个ChatBot，可以跟用户产生多轮对话，并根据用户输入和历史对话进行输出\n",
    "\n",
    "{{$chat_history}}\n",
    "用户:> {{$user_input}}\n",
    "输出:>\n",
    "\"\"\"\n",
    "\n",
    "prompt_config = sk.PromptTemplateConfig.from_completion_parameters(temperature=0.7, top_p=0.4)\n",
    "prompt_template = sk.PromptTemplate(sk_prompt, kernel.prompt_template_engine, prompt_config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "基于prompt config我们可以构造出一个semantic function用于调用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "function_config = sk.SemanticFunctionConfig(prompt_config, prompt_template)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "semantic_function除了可以直接调用以外，也可以注册到kernel之中，通过kernel进行调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_function = kernel.register_semantic_function(\"ChatBot\", \"Chat\", function_config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在以下代码中我们构造了了一个读取用户输入，并使用`sk.ContextVariables`来处理历史对话消息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "#-# cell_skip\n",
    "async def chat(context_vars: sk.ContextVariables) -> bool:\n",
    "    try:\n",
    "        user_input = input(\"用户:> \")\n",
    "        context_vars[\"user_input\"] = user_input\n",
    "    except KeyboardInterrupt:\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "    except EOFError:\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "\n",
    "    if user_input == \"exit\":\n",
    "        print(\"\\n\\nExiting chat...\")\n",
    "        return False\n",
    "\n",
    "    # 在context中不断填充历史聊天记录\n",
    "    answer = await kernel.run_async(chat_function, input_vars=context_vars)\n",
    "    context_vars[\"chat_history\"] += f\"\\用户:> {user_input}\\nChatBot:> {answer}\\n\"\n",
    "\n",
    "    print(f\"ChatBot:> {answer}\")\n",
    "    return True\n",
    "\n",
    "context = sk.ContextVariables()\n",
    "context[\"chat_history\"] = \"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构造循环实现多轮对话："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Variable `$chat_history` not found\n",
      "[INFO] [02-01 18:17:59] openapi_requestor.py:167 [t:8406866752]: async requesting llm api endpoint: /chat/eb-instant\n",
      "[INFO] [02-01 18:17:59] openapi_requestor.py:178 [t:8406866752]: requesting raw: QfRequest(method='POST', url='https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant', query={}, headers={'Request_id': 'sdk-py-0.3.0rc0-FNpioSwonxGLJwYC'}, json_body={'temperature': 0.7, 'top_p': 0.4, 'messages': [{'role': 'user', 'content': '\\n你是一个ChatBot，可以跟用户产生多轮对话，并根据用户输入和历史对话进行输出\\n\\n\\n用户:> 我是谁？\\n输出:>\\n'}], 'stream': False, 'extra_parameters': {'request_source': 'qianfan_py_sdk_v0.3.0rc0'}}, retry_config=RetryConfig(retry_count=1, timeout=60, max_wait_interval=120, backoff_factor=1, jitter=1, retry_err_codes={18, 336100}))\n",
      "[INFO] [02-01 18:17:59] oauth.py:229 [t:8406866752]: trying to refresh access_token for ak `rRlk1M***`\n",
      "[INFO] [02-01 18:17:59] oauth.py:243 [t:8406866752]: sucessfully refresh access_token for ak `rRlk1M***`\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ChatBot:> 你好，我无法确定你的身份，但我很愿意和你继续对话，了解更多关于你的信息。请告诉我更多关于你的信息，我会尽力回答你的问题。\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Variable `$user_input` not found\n",
      "[INFO] [02-01 18:18:20] openapi_requestor.py:167 [t:8406866752]: async requesting llm api endpoint: /chat/eb-instant\n",
      "[INFO] [02-01 18:18:20] openapi_requestor.py:178 [t:8406866752]: requesting raw: QfRequest(method='POST', url='https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant', query={}, headers={'Request_id': 'sdk-py-0.3.0rc0-6pgafAK0AHAkpAQT'}, json_body={'temperature': 0.7, 'top_p': 0.4, 'messages': [{'role': 'user', 'content': '\\n你是一个ChatBot，可以跟用户产生多轮对话，并根据用户输入和历史对话进行输出\\n\\n\\\\用户:> 我是谁？\\nChatBot:> 你好，我无法确定你的身份，但我很愿意和你继续对话，了解更多关于你的信息。请告诉我更多关于你的信息，我会尽力回答你的问题。\\n\\n用户:> \\n输出:>\\n'}], 'stream': False, 'extra_parameters': {'request_source': 'qianfan_py_sdk_v0.3.0rc0'}}, retry_config=RetryConfig(retry_count=1, timeout=60, max_wait_interval=120, backoff_factor=1, jitter=1, retry_err_codes={18, 336100}))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ChatBot:> 好的，请问你有什么问题或者想了解什么？我很乐意和你交流。\n",
      "\n",
      "\n",
      "Exiting chat...\n"
     ]
    }
   ],
   "source": [
    "#-# cell_skip\n",
    "async def main():\n",
    "    chatting = True\n",
    "    while chatting:\n",
    "        chatting = await chat(context)\n",
    "        \n",
    "await main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  },
  "vscode": {
   "interpreter": {
    "hash": "58f7cb64c3a06383b7f18d2a11305edccbad427293a2b4afa7abe8bfc810d4bb"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
